package com.cmc.cloud.cmclink.doc.service.impl.si;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import javax.annotation.Resource;

import org.apache.tika.utils.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.multipart.MultipartFile;

import com.cmc.cloud.cmclink.common.report.handler.ReportUrlHandler;
import com.cmc.cloud.cmclink.doc.api.document.emums.*;
import com.cmc.cloud.cmclink.doc.api.document.rep.sendarrivaldto.*;
import com.cmc.cloud.cmclink.doc.constants.CommonConstants;
import com.cmc.cloud.cmclink.doc.constants.errorconstant.ArrivalNoticeErrorCodeConstants;
import com.cmc.cloud.cmclink.doc.constants.errorconstant.BlnoFreightErrorCodeConstants;
import com.cmc.cloud.cmclink.doc.convert.BlnoArrivalNoticeConvert;
import com.cmc.cloud.cmclink.doc.entity.*;
import com.cmc.cloud.cmclink.doc.mapper.BlnoFreightMapper;
import com.cmc.cloud.cmclink.doc.mapper.OperateLogMapper;
import com.cmc.cloud.cmclink.doc.service.ArrivalnoticePrintSetupService;
import com.cmc.cloud.cmclink.doc.service.BlnoCntrService;
import com.cmc.cloud.cmclink.doc.service.si.*;
import com.cmc.cloud.cmclink.doc.util.CostChargeUtil;
import com.cmc.cloud.cmclink.doc.util.EmailUtil;
import com.cmc.cloud.cmclink.doc.util.FileOperateUtil;
import com.cmc.cloud.cmclink.doc.vo.blnovo.arrivalnoticevo.DownloadAndSendEmailVO;
import com.cmc.cloud.cmclink.doc.vo.blnovo.operatevo.BlnoSendArrivalReqVO;
import com.cmc.cloud.cmclink.svl.api.base.resp.VesselVoyRespDTO;
import com.cmes.framework.common.exception.util.ServiceExceptionUtil;
import com.cmes.framework.mybatis.core.query.LambdaQueryWrapperX;

import cn.hutool.core.collection.CollectionUtil;
import lombok.extern.slf4j.Slf4j;

/**
 * 到货通知
 * 
 * @author dev89
 * @date 2025/10/01 14:19
 **/
@Service
@Validated
@Slf4j
public class BlnoArrivalNoticeServiceImpl implements BlnoArrivalNoticeService {
    /**
     * 连接符
     */
    private static final String CNTR_CONNECT = "x";

    /**
     * Arrival Notice文件名
     */
    private static final String ARRIVAL_NOTICE_FILE_NAME = "Arrival_Notice_";

    /**
     * To Oder
     */
    private static final String CONSIGNEE_NAME_TO_ORDER = "tooder";

    /**
     * To the order
     */
    private static final String CONSIGNEE_NAME_TO_THE_ORDER = "totheorder";

    /**
     * 文件名默认后缀
     */
    private static final String DEFAULT_FILE_SUFFIX = ".pdf";

    /**
     * 邮件模板编码
     */
    private static final String TEMPLATE_CODE = "arrivalNotice";

    /**
     * 提单号构造url
     */
    private static final String BLNO_ID_URL = "&blnoId=%d&currentPort=%s&format=pdf";

    /**
     * 提单号构造url
     */
    private static final String UNKNOW_BLNO = "未知提单号";

    /**
     * 提单号构造url
     */
    private static final String BLNO_NOT_EXIST = "提单号不存在";

    /**
     * 获取ETA为空
     */
    private static final String GET_ETA_EMPTY = "获取ETA为空";

    @Value("${arrival.notice.download.url}")
    private String downloadUrl;

    @Resource
    private BlnoService blnoService;
    @Resource
    private BlnoCntrService blnoCntrService;
    @Resource
    private BlnoCargoService blnoCargoService;
    @Resource
    private BlnoVslvoyService blnoVslvoyService;
    @Resource
    private CostChargeUtil costChargeUtil;
    @Resource
    private BlnoScnService blnoScnService;
    @Resource
    private ArrivalnoticePrintSetupService arrivalnoticePrintSetupService;
    @Resource
    private EmailUtil emailUtil;

    @Resource
    private BlnoFreightMapper blnoFreightMapper;

    @Resource
    private ReportUrlHandler reportUrlHandler;

    @Resource
    private OperateLogMapper operateLogMapper;

    @Value("${spring.profiles.active:dev}") // 设置默认值为"dev"
    private String activeProfile;

    @Override
    public MultipartFile genArrivalNoticePdfUrl(String currentPort, Long blnoId, String url) {

        String generateUrl;
        try {
            generateUrl = reportUrlHandler.handle(downloadUrl);
        } catch (Exception e) {
            log.error("generateUrl failed", e);
            throw ServiceExceptionUtil.exception(ArrivalNoticeErrorCodeConstants.ARRIVAL_NOTICE_URL_FAILED);
        }

        try {
            boolean checkExistEta = checkEtaExist(blnoId, currentPort);
            if (checkExistEta) {
                url = generateUrl + String.format(BLNO_ID_URL, blnoId, currentPort) + getEnvUrl();
                MultipartFile multipartFile = downLoadAndTransitMultipart(url, blnoId);
                log.info("生成到货通知PDF文件流 - 文件名: {}, 文件大小: {} bytes", multipartFile.getOriginalFilename(),
                    multipartFile.getSize());
                return multipartFile;
            } else {
                log.info("blno has no eta time, {}", blnoId);
                // throw ServiceExceptionUtil.exception(ArrivalNoticeErrorCodeConstants.ARRIVAL_NOTICE_ETA_NULL);
            }
        } catch (Exception e) {
            log.error("caught an exception on genArrivalNoticePdfUrl, blnoId: {}", blnoId, e);
            // throw ServiceExceptionUtil.exception(ArrivalNoticeErrorCodeConstants.ARRIVAL_NOTICE_GENERATE_FAILED);
        }
        return null;
    }

    @Override
    public void sendArrivalNotice(BlnoSendArrivalReqVO req) {
        String currentPort = req.getCurrentPort();
        List<Long> blnoIds = req.getBlnoIds();
        if (CollectionUtil.isEmpty(blnoIds)) {
            return;
        }

        String generateUrl;
        try {
            generateUrl = reportUrlHandler.handle(downloadUrl);
        } catch (Exception e) {
            log.error("generateUrl failed", e);
            throw ServiceExceptionUtil.exception(ArrivalNoticeErrorCodeConstants.ARRIVAL_NOTICE_URL_FAILED);
        }

        List<OperateLogDO> operateLogList = new ArrayList<>();
        for (Long blnoId : blnoIds) {
            String message = CommonConstants.RESPONSE_SUCCESS;
            try {
                boolean checkExistEta = checkEtaExist(blnoId, currentPort);
                if (checkExistEta) {
                    String url = generateUrl + String.format(BLNO_ID_URL, blnoId, currentPort) + getEnvUrl();
                    DownloadAndSendEmailVO sendEmail = new DownloadAndSendEmailVO();
                    sendEmail.setBlnoId(blnoId);
                    sendEmail.setUrl(url);
                    List<DownloadAndSendEmailVO> sendEmailList = new ArrayList<>();
                    sendEmailList.add(sendEmail);
                    downloadAndSendEmail(sendEmailList);
                } else {
                    log.info("blno has no eta time, {}", blnoId);
                    message = GET_ETA_EMPTY;
                }
            } catch (Exception e) {
                log.error("caught an exception on sendArrivalNotice, blnoId: {}", blnoId, e);
                message = e.toString();
            }
            OperateLogDO operateLog = buildOperate(blnoId, message);
            operateLogList.add(operateLog);
        }

        // 保存日志
        operateLogMapper.insertBatch(operateLogList);
    }

    private String getEnvUrl() {
        String env = activeProfile.split(CommonConstants.CSV_DELIMITER)[0];
        return "&tag=" + env;
    }

    private OperateLogDO buildOperate(Long blnoId, String message) {
        OperateLogDO operateLog = new OperateLogDO();
        operateLog.setBusinessId(blnoId);
        BlnoDO blno = blnoService.getBlno(blnoId);
        if (blno == null) {
            operateLog.setBusinessNo(UNKNOW_BLNO);
            operateLog.setOperateDesc(BLNO_NOT_EXIST);
        } else {
            operateLog.setBusinessNo(blno.getBlNo());
            operateLog.setOperateDesc(message);
        }
        operateLog.setBusinessType(BusinessTypeEnum.SI.getValue());
        operateLog.setOperateType(SiOperateTypeEnum.ARRIVAL_NOTICE.getValue());

        return operateLog;
    }

    @Override
    public BlnoArrivalNoticeDTO queryArrivalNotice(BlnoSendArrivalReqDTO req) {
        Long id = req.getBlnoId();
        String currentPort = req.getCurrentPort();
        BlnoDO blno = blnoService.getBlno(id);
        List<BlnoCntrDO> cntrList = blnoCntrService.getBlnoCntrByBlnoId(id);
        List<BlnoCargoDO> cargoList = blnoCargoService.getBlnoCargoByBlnoId(id);
        BlnoCargoDO cargo = cargoList.get(0);
        List<BlnoVslvoyDO> vslvoyList = blnoVslvoyService.getBlnoVslvoyBySiId(id);
        List<BlnoFreightDO> freightList = getFreightByBlnoId(id);
        ArrivalnoticePrintSetupDO arrivalNoticeDo =
            arrivalnoticePrintSetupService.getArrivalnoticePrintSetupByPortCode(currentPort);

        VesselVoyRespDTO respDto =
            costChargeUtil.getVoyQueryRespDTO(currentPort, ExImTypeEnum.IM.getValue(), vslvoyList);
        if (StringUtils.isEmpty(respDto.getEta())) {
            throw ServiceExceptionUtil.exception(BlnoFreightErrorCodeConstants.BLNO_FREIGHT_ETU_IS_NULL);
        }
        return buildBlnoArrivalNotice(blno, cntrList, cargo, vslvoyList, respDto.getEta(), currentPort, arrivalNoticeDo,
            freightList);
    }

    private boolean checkEtaExist(Long id, String currentPort) {
        List<BlnoVslvoyDO> vslvoyList = blnoVslvoyService.getBlnoVslvoyBySiId(id);
        VesselVoyRespDTO respDto = null;
        try {
            respDto = costChargeUtil.getVoyQueryRespDTO(currentPort, ExImTypeEnum.IM.getValue(), vslvoyList);
        } catch (Exception e) {
            log.error("caught an exception on getVoyQueryRespDTO");
        }
        return respDto != null && !StringUtils.isEmpty(respDto.getEta());
    }

    @Override
    public void downloadAndSendEmail(List<DownloadAndSendEmailVO> reqList) {
        if (CollectionUtil.isEmpty(reqList)) {
            return;
        }
        for (DownloadAndSendEmailVO req : reqList) {
            String downLoadUrl = req.getUrl();
            if (StringUtils.isEmpty(downLoadUrl)) {
                continue;
            }
            Long id = req.getBlnoId();
            MultipartFile multipartFile = downLoadAndTransitMultipart(downLoadUrl, id);
            PrintScnDTO consignee = new PrintScnDTO();
            PrintScnDTO notify = new PrintScnDTO();
            List<BlnoScnDO> scnList = blnoScnService.getBlnoScnByBlnoId(id);
            for (BlnoScnDO scn : scnList) {
                PrintScnDTO printScn = buildPrintScnDTO(scn);
                String partyType = scn.getPartyTypeCode();
                if (PartyTypeCodeEnum.NOTIFY.getValue().equals(partyType)) {
                    notify = printScn;
                }
                if (PartyTypeCodeEnum.CONSIGNEE.getValue().equals(partyType)) {
                    consignee = printScn;
                }
            }

            sendEmail(consignee, notify, id, multipartFile);
        }
    }

    private List<BlnoFreightDO> getFreightByBlnoId(Long blonId) {
        LambdaQueryWrapperX<BlnoFreightDO> queryWrapperX = new LambdaQueryWrapperX<>();
        queryWrapperX.eq(BlnoFreightDO::getPayment, PaymentEnum.C.getValue()).eq(BlnoFreightDO::getBlnoId, blonId);
        return blnoFreightMapper.selectList(queryWrapperX);
    }

    private List<BlnoFreightDO> getFreightByBlnoIdList(List<Long> blonIds) {
        LambdaQueryWrapperX<BlnoFreightDO> queryWrapperX = new LambdaQueryWrapperX<>();
        queryWrapperX.eq(BlnoFreightDO::getPayment, PaymentEnum.C.getValue()).in(BlnoFreightDO::getBlnoId, blonIds);
        return blnoFreightMapper.selectList(queryWrapperX);
    }

    private BlnoArrivalNoticeDTO buildBlnoArrivalNotice(BlnoDO blno, List<BlnoCntrDO> cntrList, BlnoCargoDO cargo,
        List<BlnoVslvoyDO> vslvoyList, String eta, String currentPort, ArrivalnoticePrintSetupDO arrivalNoticeDo,
        List<BlnoFreightDO> freightList) {
        BlnoArrivalNoticeDTO arrivalNotice = BlnoArrivalNoticeConvert.INSTANCE.convert(blno);
        arrivalNotice.setEta(eta);
        buildVslvoyRelation(arrivalNotice, currentPort, vslvoyList);
        buildCntrRelation(arrivalNotice, cntrList);
        buildCargoRelation(arrivalNotice, cargo);
        buildScnRelation(arrivalNotice, blno.getId());
        buildPleaseContact(arrivalNotice, arrivalNoticeDo);
        buildFreight(arrivalNotice, freightList);
        return arrivalNotice;
    }

    private void buildFreight(BlnoArrivalNoticeDTO arrivalNotice, List<BlnoFreightDO> freightList) {
        if (CollectionUtil.isEmpty(freightList)) {
            return;
        }
        Map<String, List<BlnoFreightDO>> freightChargeCodeMap =
            freightList.stream().collect(Collectors.groupingBy(BlnoFreightDO::getChargeCode));
        List<String> freightStrList = new ArrayList<>();
        for (Map.Entry<String, List<BlnoFreightDO>> entry : freightChargeCodeMap.entrySet()) {
            String chargeCode = entry.getKey();
            List<BlnoFreightDO> freightTmpList = entry.getValue();
            StringBuilder freightSb = new StringBuilder();
            freightSb.append(chargeCode).append(CommonConstants.COLON);
            for (BlnoFreightDO freightTmp : freightTmpList) {
                freightSb.append(freightTmp.getCurrency()).append(freightTmp.getAmount());
                String per = freightTmp.getPer();
                if (CalculatePerEnum.CNTR.getValue().equals(per)) {
                    freightSb.append(CommonConstants.UNDERSCORE).append(freightTmp.getCntrSize())
                        .append(freightTmp.getCntrType()).append(CommonConstants.SEMICOLON);
                }
            }
            freightSb.deleteCharAt(freightSb.length() - 1);
            freightStrList.add(freightSb.toString());
        }

        arrivalNotice.setFreights(String.join(CommonConstants.CSV_DELIMITER, freightStrList));
    }

    private void buildPleaseContact(BlnoArrivalNoticeDTO arrivalNotice, ArrivalnoticePrintSetupDO arrivalNoticeDo) {
        PleaseContactDTO pleaseContact = BlnoArrivalNoticeConvert.INSTANCE.convert(arrivalNoticeDo);
        arrivalNotice.setPleaseContact(pleaseContact);
    }

    private void buildScnRelation(BlnoArrivalNoticeDTO arrivalNotice, Long blnoId) {
        List<BlnoScnDO> scnList = blnoScnService.getBlnoScnByBlnoId(blnoId);
        for (BlnoScnDO scn : scnList) {
            PrintScnDTO printScn = buildPrintScnDTO(scn);
            String partyType = scn.getPartyTypeCode();
            if (PartyTypeCodeEnum.SHIPPER.getValue().equals(partyType)) {
                arrivalNotice.setShipper(printScn);
            }
            if (PartyTypeCodeEnum.NOTIFY.getValue().equals(partyType)) {
                arrivalNotice.setNotifyParty(printScn);
            }
            if (PartyTypeCodeEnum.CONSIGNEE.getValue().equals(partyType)) {
                arrivalNotice.setConsignee(printScn);
            }
        }
    }

    private PrintScnDTO buildPrintScnDTO(BlnoScnDO scn) {
        PrintScnDTO printScn = new PrintScnDTO();
        printScn.setCompanyName(scn.getCompanyName());
        if (scn.getAddressPrintFlag() != null && scn.getAddressPrintFlag()) {
            printScn.setCompanyAddress(scn.getCompanyAddress());
        }
        if (scn.getEmailPrintFlag() != null && scn.getEmailPrintFlag()) {
            printScn.setEmail(scn.getEmail());
        }
        if (scn.getFaxPrintFlag() != null && scn.getFaxPrintFlag()) {
            printScn.setFax(scn.getFax());
        }
        if (scn.getTelephonePrintFlag() != null && scn.getTelephonePrintFlag()) {
            printScn.setTelephone(scn.getTelephone());
        }
        return printScn;
    }

    private void buildCargoRelation(BlnoArrivalNoticeDTO arrivalNotice, BlnoCargoDO cargo) {
        CargoInfoDTO cargoInfo = BlnoArrivalNoticeConvert.INSTANCE.convert(cargo);
        arrivalNotice.setCargoInfo(cargoInfo);
    }

    private void buildCntrRelation(BlnoArrivalNoticeDTO arrivalNotice, List<BlnoCntrDO> cntrList) {
        if (CollectionUtil.isEmpty(cntrList)) {
            return;
        }
        List<String> cntrInfoList = cntrList.stream()
            .map(
                cntr -> cntr.getCntrNo() + " " + cntr.getCntrSize() + " " + cntr.getCntrType() + " " + cntr.getSealNo())
            .collect(Collectors.toList());
        arrivalNotice.setCntrInfos(String.join(CommonConstants.CSV_DELIMITER, cntrInfoList));

        Map<String, Long> nameCountMap = cntrList.stream()
            .collect(Collectors.groupingBy(cntr -> cntr.getCntrSize() + cntr.getCntrType(), Collectors.counting()));
        List<String> totalNumberList = nameCountMap.entrySet().stream()
            .map(entry -> entry.getValue() + CNTR_CONNECT + entry.getKey()).collect(Collectors.toList());
        String joinCntrs = "";
        if (!CollectionUtil.isEmpty(totalNumberList)) {
            joinCntrs = String.join(CommonConstants.SEMICOLON, totalNumberList);
        }
        arrivalNotice.setTotalNumberCntrs(joinCntrs);
    }

    private void buildVslvoyRelation(BlnoArrivalNoticeDTO arrivalNotice, String currentPort,
        List<BlnoVslvoyDO> vslvoyList) {
        BlnoVslvoyDO curVslvoy = buildCurrentVslvoy(currentPort, vslvoyList);
        arrivalNotice.setVesselCode(curVslvoy.getVesselCode());
        arrivalNotice.setVoyage(curVslvoy.getVoyageIm());
        arrivalNotice.setTerminal(curVslvoy.getStuffLocIm());
    }

    private BlnoVslvoyDO buildCurrentVslvoy(String currentPort, List<BlnoVslvoyDO> vslvoyList) {
        for (BlnoVslvoyDO vslvoy : vslvoyList) {
            if (currentPort.equals(vslvoy.getPodCode())) {
                return vslvoy;
            }
        }
        return new BlnoVslvoyDO();
    }

    private MultipartFile downLoadAndTransitMultipart(String url, Long blnoId) {
        String fileName = ARRIVAL_NOTICE_FILE_NAME + blnoId + DEFAULT_FILE_SUFFIX;
        String savePath = FileOperateUtil.getResourcePath() + fileName;
        FileOperateUtil.downloadWithCommonsIo(url, savePath);
        File file = new File(savePath);
        return FileOperateUtil.ofMultipartFile(file);
    }

    private void sendEmail(PrintScnDTO consignee, PrintScnDTO notify, Long blnoId, MultipartFile file) {
        if (consignee == null || notify == null) {
            throw ServiceExceptionUtil.exception(ArrivalNoticeErrorCodeConstants.ARRIVAL_NOTICE_CONSIGNEE_NOTIFY_NULL);
        }
        String email;
        String consigneeName = consignee.getCompanyName();
        boolean checkConsigneeName = !StringUtils.isEmpty(consigneeName)
            && (consigneeName.replace(" ", "").toLowerCase().startsWith(CONSIGNEE_NAME_TO_ORDER)
                || consigneeName.replace(" ", "").toLowerCase().startsWith(CONSIGNEE_NAME_TO_THE_ORDER));
        if (checkConsigneeName) {
            email = notify.getEmail();
        } else {
            email = consignee.getEmail();
        }
        if (email == null) {
            log.error("send email null, blnoId: {}", blnoId);
            throw ServiceExceptionUtil.exception(ArrivalNoticeErrorCodeConstants.ARRIVAL_NOTICE_EMAIL_NULL);
        }
        Map<String, Object> templateParams = new HashMap<>(CommonConstants.DEFAULT_MAP_COUNT);
        templateParams.put("test", "杜甫--登高\n" + "风急天高猿啸哀\n" + "渚清沙白鸟飞回\n" + "无边落木萧萧下\n" + "不尽长江滚滚来\n" + "万里悲秋常作客\n"
            + "百年多病独登台\n" + "艰难苦恨繁霜鬓\n" + "潦倒新停浊酒杯");
        List<MultipartFile> files = new ArrayList<>();
        files.add(file);
        emailUtil.sendMail(email, TEMPLATE_CODE, templateParams, files);
    }
}
